
package com.studiofortress.sf.audio;

import java.util.LinkedList;

/**
 *
 * @author Joseph Lenton - JosephLenton@StudioFortress.com
 */
class DynamicAudioBuffer implements AudioBuffer
{
    private static final int DEFAULT_BUFFER_SEGMENT_SIZE = 2048;

    private LinkedList<byte[]> buffer;
    private int bufferIndex;
    private boolean finished;
    private final int bufferSegmentSize;
    
    public DynamicAudioBuffer()
    {
        this( DEFAULT_BUFFER_SEGMENT_SIZE );
    }

    public byte getByte(int location)
    {
        final int segmentI = (int) Math.floor((float)location / (float)bufferSegmentSize);
        final int bufferI = location % bufferSegmentSize;

        return buffer.get( segmentI )[ bufferI ];
    }

    public DynamicAudioBuffer(int bufferSegmentSize)
    {
        this.bufferSegmentSize = bufferSegmentSize;
        this.buffer = new LinkedList<byte[]>();
        this.bufferIndex = 0;
        this.finished = false;
    }
    
    public void addBuffer(byte[] audio)
    {
        addBuffer( audio, audio.length );
    }
    
    public synchronized void addBuffer(byte[] audio, final int audioLength)
    {
        if (isFinished()) {
            throw new IllegalStateException("Cannot add data to an AudioBuffer that is finished.");
        }
        
        int audioI = 0;
        final int newBufferIndex = bufferIndex + audioLength;
        final int spaceLeftInLastBuffer = (bufferIndex % bufferSegmentSize);
        
        if (spaceLeftInLastBuffer != 0) {
            final byte[] lastBuffer = buffer.getLast();
            final int bufferSpace = Math.min(lastBuffer.length - spaceLeftInLastBuffer, audioLength);
            System.arraycopy(audio, 0, lastBuffer, spaceLeftInLastBuffer, bufferSpace);
            audioI += bufferSpace;
        }

        while (audioI < audioLength) {
            final byte[] newBuffer = new byte[bufferSegmentSize];
            final int copyLength = Math.min( newBuffer.length, audioLength-audioI );

            System.arraycopy( audio, audioI, newBuffer, 0, copyLength );
            buffer.addLast( newBuffer );
            audioI += bufferSegmentSize;
        }
        
        bufferIndex = newBufferIndex;
    }
    
    public synchronized byte[] getBuffer(final int start, int length)
    {
        final int returnBufferSize = Math.min( length, bufferIndex-start );
        final byte[] returnBuffer = new byte[ returnBufferSize ];

        int segmentIndex = (int) Math.floor((float)start / (float)bufferSegmentSize);
        int segmentBufferIndex = start % bufferSegmentSize;

        int returnBufferI = 0;
        while ( length > 0 && segmentIndex < buffer.size() ) {
            final byte[] bufferSegment = buffer.get( segmentIndex );
            final int copySize = Math.min(length, bufferSegmentSize-segmentBufferIndex);
//System.out.println("segmentBufferIndex: " + segmentBufferIndex);
//System.out.println("returnBufferI     : " + returnBufferI);
//System.out.println("copySize          : " + copySize);
            System.arraycopy( bufferSegment, segmentBufferIndex, returnBuffer, returnBufferI, copySize );

            length -= copySize;
            returnBufferI += copySize;
            segmentBufferIndex = 0;
            segmentIndex++;
        }

        return returnBuffer;
    }

    /**
     * @return The number of bytes stored in this buffer.
     */
    public int size()
    {
        return bufferIndex;
    }

    /**
     * Compacts down the internal buffer so that it fits into only the number of
     * bytes used.
     */
    public synchronized void finish()
    {
        if (isFinished()) {
            throw new IllegalStateException("Finished should only be called the once.");
        }

        finished = true;
    }
    
    /**
     * @return True if this buffer is finished with and no more data will be added to it.
     */
    public synchronized boolean isFinished()
    {
        return finished;
    }
}
